﻿Imports System.Net
Imports System.Xml

Namespace Scraping
    Partial Class TwitterClient
        Implements ITwitterClient
        Private Const StrInvalidUserListTypeReservated As String = "不正なUserListTypeが指定されました。"
        Private Enum UserListType
            Friends
            Followers
        End Enum

        ''' <summary>
        ''' すべてのページから自分がフォローしているユーザーのコレクションを取得します。
        ''' </summary>
        ''' <returns>ユーザーのコレクション</returns>
        ''' <remarks></remarks>
        Public Function GetFriends() As IList(Of TwitterUser) Implements ITwitterClient.GetFriends
            Return GetFriends(0, 0)
        End Function

        ''' <summary>
        ''' ページを指定して自分がフォローしているユーザーのコレクションを取得します。
        ''' </summary>
        ''' <param name="beginPage">取得開始ページ</param>
        ''' <param name="endPage">取得終了ページ</param>
        ''' <returns>ユーザーのコレクション</returns>
        ''' <remarks>
        ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
        ''' </remarks>
        Public Function GetFriends(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser) Implements ITwitterClient.GetFriends
            Return GetUsers(beginPage, endPage, UserListType.Friends)
        End Function

        ''' <summary>
        ''' すべてのページから自分をフォローしているユーザーのコレクションを取得します。
        ''' </summary>
        ''' <returns>ユーザーのコレクション</returns>
        ''' <remarks></remarks>
        Public Function GetFollowers() As IList(Of TwitterUser) Implements ITwitterClient.GetFollowers
            Return GetFollowers(0, 0)
        End Function

        ''' <summary>
        ''' ページを指定して自分をフォローしているユーザーのコレクションを取得します。
        ''' </summary>
        ''' <param name="beginPage">取得開始ページ</param>
        ''' <param name="endPage">取得終了ページ</param>
        ''' <returns>フォローしているユーザーのコレクション</returns>
        ''' <remarks>
        ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
        ''' </remarks>
        Public Function GetFollowers(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser) Implements ITwitterClient.GetFollowers
            Return GetUsers(beginPage, endPage, UserListType.Followers)
        End Function

        Private Function GetUsers(ByVal beginPage As Integer, ByVal endPage As Integer, ByVal type As UserListType) As IList(Of TwitterUser)
            If Not IsLogined Then
                Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
            End If
            If beginPage > endPage And endPage > 0 Then
                Throw New ArgumentOutOfRangeException("endPage", "endPageには0以下またはbeginPage以上の値のみ指定できます。")
            End If

            Dim lstUsers As New List(Of TwitterUser)
            Dim pageNo As Integer
            If (beginPage > 0) Then
                pageNo = beginPage
            Else
                pageNo = 1
            End If
            While endPage = 0 OrElse pageNo <= endPage
                Dim url As String
                Select Case type
                    Case UserListType.Friends
                        url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFriendsPath, pageNo)
                    Case UserListType.Followers
                        url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFollowersPath, pageNo)
                    Case Else
                        Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
                End Select
                Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
                req.SetTwitterGetHeaders(Nothing, _cookies)
                Dim xmlRes As XmlDocument = Nothing
                Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                    Select Case res.StatusCode
                        Case HttpStatusCode.OK
                            res.LoadCookies(_cookies)
                            xmlRes = res.GetResponseAsXHtml()
                        Case Else
                            Return Nothing
                    End Select
                End Using
                Dim xnm As XmlNamespaceManager = xmlRes.GetXHtmlNameSpaceManager("x")
                Dim nodes As XmlNodeList = xmlRes.SelectNodes("//x:tr[starts-with(@id,'user_')]", xnm)
                For Each node As XmlNode In nodes
                    Dim user As TwitterUser = ParseUser(node, xnm, "x", type)
                    lstUsers.Add(user)
                Next
                If nodes.Count = 0 Then
                    Exit While
                End If
                Dim nodesNextLink As XmlNodeList = xmlRes.SelectNodes("//x:div[@id='pagination']/x:a[contains(@rel,'next')]", xnm)
                If (nodesNextLink.Count > 0) Then
                    pageNo += 1
                Else
                    Exit While
                End If
            End While
            Return lstUsers
        End Function

        ''' <summary>
        ''' レスポンスに含まれるユーザー情報を解析します。
        ''' </summary>
        ''' <param name="node">ユーザーのルートノード</param>
        ''' <param name="xnm">ノードに関連づけられたXmlNamespaceManager</param>
        ''' <param name="prefix">プレフィックス</param>
        ''' <param name="type">ユーザーリストの種類</param>
        ''' <returns>解析されたユーザー情報</returns>
        ''' <remarks></remarks>
        Private Shared Function ParseUser(ByVal node As XmlNode, ByVal xnm As XmlNamespaceManager, ByVal prefix As String, ByVal type As UserListType) As TwitterUser
            Dim user As TwitterUser
            user = New TwitterUser()
            user.UserID = CLng(node.Attributes("id").Value.Substring(5))
            Dim nodeAddress As XmlNode
            Select Case type
                Case UserListType.Friends
                    nodeAddress = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:address[@class='about vcard']", prefix), xnm)
                Case UserListType.Followers
                    nodeAddress = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:address[@class='about vcard']", prefix), xnm)
                Case Else
                    Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
            End Select
            Dim nodeScreenName As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span[@class='label screenname']/{0}:a", prefix), xnm)
            user.Language = nodeScreenName.Attributes("hreflang").Value
            user.UserName = nodeScreenName.InnerText
            Dim nodeFullName As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span/{0}:span[@class='label fullname']", prefix), xnm)
            If nodeFullName IsNot Nothing Then
                user.DisplayName = nodeFullName.InnerText
            End If
            Dim nodeLocation As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span/{0}:span[@class='location']", prefix), xnm)
            If nodeLocation IsNot Nothing Then
                user.Location = nodeLocation.InnerText
                If nodeFullName IsNot Nothing Then
                    user.Location = user.Location.Substring(3)
                End If
            End If
            Dim nodeThumb As XmlNode = node.SelectSingleNode(String.Format("{0}:td[@class='thumb vcard']/{0}:a/{0}:img", prefix), xnm)
            user.IconUrl = nodeThumb.Attributes("src").Value
            ' NOTICE:自己紹介は取得できなくなりました。
            Dim nodeCurrently As XmlNode = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:span[@class='user-body']/{0}:span[@class='currently']", prefix), xnm)
            If nodeCurrently IsNot Nothing Then
                user.Currently = nodeCurrently.InnerText.TrimStart()
            End If
            user.IsProtected = node.Attributes("class").Value.Contains("protected")
            Select Case type
                Case UserListType.Friends
                    user.IsFriend = True
                    user.IsFollower = node.Attributes("class").Value.Contains("direct-message-able")
                Case UserListType.Followers
                    user.IsFollower = True
                    user.IsFriend = node.Attributes("class").Value.Contains("following")
            End Select
            Return user
        End Function
    End Class
End Namespace